/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/error.hpp>
#include <baja/math.hpp>

#include <baja/graphics/graphics_fwd.hpp>
#include <baja/graphics/graphics_interfaces.hpp>
#include <baja/graphics/graphics_error.hpp>
#include <baja/graphics/graphics_types.hpp>

#include <baja/stopwatch.hpp>

namespace baja { namespace graphics {

namespace m = baja::math;

class render_service : 
    public irender_service, 
    public std::enable_shared_from_this<irender_service>,
    protected eventable<resize_event_args>, 
    protected eventable<event_args>
{
public:
    render_service();

    static std::shared_ptr<render_service> create()
    {
        std::shared_ptr<render_service> rs = std::make_shared<render_service>();
        return rs;
    }

    // rendering

    virtual void addRenderObject(
        const std::shared_ptr<irender_object>& renderObject
        );

    virtual void removeRenderObject(
        const std::shared_ptr<irender_object>& renderObject
        );

    virtual void render(
        const std::shared_ptr<iengine>& graphics
        );

    virtual float32 getFramesPerSecond();

    // resizing

    virtual int32 addResizeHandler(const event_source<resize_event_args>::handler_type& handler, void* context = nullptr);
    virtual void removeResizeHandler(const int32 cookie);

    virtual void resize(
        const std::shared_ptr<iengine>& graphics
        );

    // graphics initialization

    virtual int addInitializeHandler(const event_source<event_args>::handler_type& handler, void* context = nullptr);
    virtual void removeInitializeHandler(const int32 cookie);
    virtual int addTeardownHandler(const event_source<event_args>::handler_type& handler, void* context = nullptr);
    virtual void removeTeardownHandler(const int32 cookie);

    virtual void initialize(const std::shared_ptr<iengine>& graphics);
    virtual void teardown(const std::shared_ptr<iengine>& graphics);

private:
    typedef std::pair<int32, std::weak_ptr<irender_object>> render_object_pair;
    typedef std::multimap<int32, std::weak_ptr<irender_object>> render_object_map;

    render_object_map _renderObjects;
    stopwatch _fpsStopwatch;
    float32 _fps;
    uint32 _frameCount;
    event_source<resize_event_args> _resizeEventSource;
    event_source<event_args> _initializeEventSource;
    event_source<event_args> _teardownEventSource;
};

// constructor
inline render_service::render_service() : _fps(0.f), _frameCount(0) 
{
    _fpsStopwatch.reset();
}

// add a render object
inline void render_service::addRenderObject(const std::shared_ptr<irender_object>& renderObject)
{
    // get the render priority
    int32 priority = renderObject->getPriority();

    // add into the multimap
    render_object_pair entry;
    entry.first = priority;
    entry.second = renderObject;

    _renderObjects.insert(entry);

    renderObject->onAddedToRenderService(shared_from_this());
}

// removes a render object
inline void render_service::removeRenderObject(const std::shared_ptr<irender_object>& renderObject)
{
    BAJA_CHECK_ARG(renderObject);

    // find the render object and remove from the multimap
    auto iter = _renderObjects.begin();
    while (iter != _renderObjects.end())
    {
        if (iter->second.lock() == renderObject)
        {
            _renderObjects.erase(iter);
            break;
        }

        iter++;
    }

    renderObject->onRemovedFromRenderService(shared_from_this());
}

// renders all render objects 
inline void render_service::render(const std::shared_ptr<iengine>& graphics)
{
    // find the render object and remove from the multimap
    auto iter = _renderObjects.begin();
    while (iter != _renderObjects.end())
    {
        iter->second.lock()->render(graphics); // NOTE: this was casted to TRACEHR
        iter++;
    }

    // compute FPS
    const double fpsCounterReset = 1.0;
    const double fpsCounterSampleSize = 0.5;
    double elapsedSeconds = _fpsStopwatch.getElapsedSeconds();
    if (elapsedSeconds >= fpsCounterReset)
    {
        // too much time has past since last frame draw, so reset counters
        _fpsStopwatch.reset();
        _frameCount = 0;
    }
    else
    {
        _frameCount++;
        if (elapsedSeconds >= fpsCounterSampleSize)
        {
            // compute new FPS
            _fps = (float)(_frameCount / elapsedSeconds);
            _fpsStopwatch.reset();
            _frameCount = 0;
        }
    }
}

// gets the frames per second
inline float32 render_service::getFramesPerSecond()
{
    return _fps;
}

inline int32 render_service::addResizeHandler(const event_source<resize_event_args>::handler_type& handler, void* context)
{
    return _resizeEventSource.addEventHandler(handler, context);
}

inline void render_service::removeResizeHandler(const int32 cookie)
{
    _resizeEventSource -= cookie;
}

inline int32 render_service::addInitializeHandler(const event_source<event_args>::handler_type& handler, void* context)
{
    return _initializeEventSource.addEventHandler(handler, context);
}

inline void render_service::removeInitializeHandler(const int32 cookie)
{
    _initializeEventSource -= cookie;
}

inline int32 render_service::addTeardownHandler(const event_source<event_args>::handler_type& handler, void* context)
{
    return (_teardownEventSource += handler);
}

inline void render_service::removeTeardownHandler(const int32 cookie)
{
    _teardownEventSource -= cookie;
}

// call all handlers for resize
inline void render_service::resize(const std::shared_ptr<iengine>& graphics)
{
    viewport vp;
    graphics->getViewport(&vp);

    resize_event_args args(this);
    args.width = static_cast<int32>(vp.width);
    args.height = static_cast<int32>(vp.height);

    this->eventable<resize_event_args>::raise(_resizeEventSource, args);
}

// call all handlers for initialize
inline void render_service::initialize(const std::shared_ptr<iengine>& graphics)
{
    this->resize(graphics);

    this->eventable<event_args>::raise(_initializeEventSource, event_args(this));
}

inline void render_service::teardown(const std::shared_ptr<iengine>& graphics)
{
    this->eventable<event_args>::raise(_teardownEventSource, event_args(this));
}

// creates an irender_service object
inline std::shared_ptr<irender_service> createRenderService()
{
    std::shared_ptr<render_service> returnValue = render_service::create();
    return std::dynamic_pointer_cast<irender_service>(returnValue);
}

}}
